Referências

# pacotes ---------------
library(tidyverse)
library(GGally)

Conexão R e SQL

# Exemplo de conexão com um MariaDB (também conhecido como MySQL) remoto.
con_mariadb <- DBI::dbConnect(
  RMariaDB::MariaDB(), 
  host = "relational.fit.cvut.cz", 
  port = 3306, 
  username = "guest", 
  password = "relational",
  dbname = "financial"
)
DBI::dbListTables(con_mariadb)
[1] "account"  "card"     "client"   "disp"     "district" "loan"     "order"    "trans"   
# Conexao com SQLite --------------------------------------------------------------
con <- DBI::dbConnect(RSQLite::SQLite(), "dados_consultoria.db")
# Acessando o SQL a partir do R ------------------------------------------------
# computação é feita lá no servidor.
tbl(con, "indicadores") %>%
  count(id)
# dá pra consultar a query de SQL que rodou lá no servidor.
tbl(con, "indicadores") %>%
  count(id) %>%
  show_query()
<SQL>
SELECT `id`, COUNT(*) AS `n`
FROM `indicadores`
GROUP BY `id`
# passando do SQL pro R --------------------------------------------------------
# collect() faz o download pro computado local (não estamos mais usando o servidor).
indicadores <- tbl(con, "indicadores") %>% collect()
# PS: o SQLite não tem formato de datas, então tem que transformar em data quando vem pro R. Esse problema não tem no MySQL ou no SQL Server.
indicadores <- indicadores %>%
  mutate(
    data = as.Date(data)
  )

Exemplo de manipulação de dados

Informação 1 - Sobre os IDs, o cliente informou que deveria ter apenas uma linha para cada trinca (id-ano-mes). Por conta de uma inconsistência, poderia acontecer de virem duas ou mais linhas para o mesma trinca (id-ano-mes). O correto é ter apenas uma linha apenas. Eles disseram que a linha com o maior valor de agendamento tem mais chance de ser a correta.

# Solução: arrange() + distinct()
indicadores <- indicadores %>%
  dplyr::arrange(desc(agendamento)) %>%
  dplyr::distinct(id, ano, mes, .keep_all = TRUE)

Informação 2 - Sobre as séries mensais, o cliente informou que:

  1. IDs podem ter início e fim distintos.
  2. A série de meses de um ID não teve ter mês faltante entre seu início e seu fim, porém, em virtude de problemas técnicos, pode haver perda de informação no meio do processo. Assim, nesses casos, orienta-se substituir o valor faltante pelo valor do mês anterior.
# Olhando o problema dos meses faltantes
indicadores %>%
  ggplot(aes(x = data, y = id, colour = id)) +
  geom_point(size = 5) 

# Solução: {padr} + {tidyr} (exemplo com o id 970)
indicadores %>%
  dplyr::filter(id == 970) %>%
  dplyr::arrange(data) %>%
  padr::pad(interval = "month", group = "id")
indicadores_com_pad <- indicadores  %>%
  padr::pad(interval = "month", group = "id")  
# padr::pad() consertou
indicadores_com_pad %>%
  ggplot(aes(x = data, y = id, colour = id)) +
  geom_point(size = 5) 

# agora tem que preencher os NAs com fill.
indicadores_com_pad <- indicadores_com_pad %>%
  dplyr::arrange(id, data) %>%
  tidyr::fill(agendamento:cidade) %>%
  dplyr::mutate(
    # mes e ano não dá pra preencher com fill diretamente
    mes = as.character(lubridate::month(data)),
    ano = as.character(lubridate::year(data))
  )

Exemplos de Criação de Features janeladas

 # features no tempo (exemplo) ---------------------
teste <- indicadores_com_pad %>% 
  filter(id %in% c(406, 420), data %>% between(as.Date("2019-01-01"), as.Date("2019-04-01"))) %>%
  select(id, data)
# transformacoes
teste %>%
  arrange(id, data) %>%
  group_by(id) %>%
  mutate(
    x = 1:n(),
    a = cumsum(x),
    b = lag(x),
    c = lag(x, n = 2),
    d = lead(x),
    e = slider::slide_dbl(x, mean, .before = 1, .after = 0),
    f = slider::slide_dbl(x, mean, .before = 0, .after = 0),
    h = x/lag(x)
  )

Fluxo de Trabalho do Tidymodels

PASSO 0) CARREGAR AS BASES

# https://github.com/gastonstat/CreditScoring 
# http://bit.ly/2kkBFrk
library(modeldata)
data(credit_data)
glimpse(credit_data) # German Risk
Rows: 4,454
Columns: 14
$ Status    <fct> good, good, bad, good, good, good, good, good, good, bad, good, good, good, good, bad, good, good, good,~
$ Seniority <int> 9, 17, 10, 0, 0, 1, 29, 9, 0, 0, 6, 7, 8, 19, 0, 0, 15, 33, 0, 1, 2, 5, 1, 27, 26, 12, 19, 15, 3, 0, 4, ~
$ Home      <fct> rent, rent, owner, rent, rent, owner, owner, parents, owner, parents, owner, owner, owner, priv, other, ~
$ Time      <int> 60, 60, 36, 60, 36, 60, 60, 12, 60, 48, 48, 36, 60, 36, 18, 24, 24, 24, 48, 60, 60, 60, 60, 60, 60, 36, ~
$ Age       <int> 30, 58, 46, 24, 26, 36, 44, 27, 32, 41, 34, 29, 30, 37, 21, 68, 52, 68, 36, 31, 25, 22, 45, 41, 51, 54, ~
$ Marital   <fct> married, widow, married, single, single, married, married, single, married, married, married, married, m~
$ Records   <fct> no, no, yes, no, no, no, no, no, no, no, no, no, no, no, yes, no, no, no, no, no, no, no, no, no, no, no~
$ Job       <fct> freelance, fixed, freelance, fixed, fixed, fixed, fixed, fixed, freelance, partime, freelance, fixed, fi~
$ Expenses  <int> 73, 48, 90, 63, 46, 75, 75, 35, 90, 90, 60, 60, 75, 75, 35, 75, 35, 65, 45, 35, 46, 45, 105, 74, 45, 60,~
$ Income    <int> 129, 131, 200, 182, 107, 214, 125, 80, 107, 80, 125, 121, 199, 170, 50, 131, 330, 200, 130, 137, 107, 32~
$ Assets    <int> 0, 0, 3000, 2500, 0, 3500, 10000, 0, 15000, 0, 4000, 3000, 5000, 3500, 0, 4162, 16500, 5000, 750, 0, 0, ~
$ Debt      <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2500, 260, 0, 0, 0, 2000, 0, 0, 0, 0, 500, 0, 0, 0, 0, 0, 0, NA, 330~
$ Amount    <int> 800, 1000, 2000, 900, 310, 650, 1600, 200, 1200, 1200, 1150, 650, 1500, 600, 400, 900, 1500, 600, 1100, ~
$ Price     <int> 846, 1658, 2985, 1325, 910, 1645, 1800, 1093, 1957, 1468, 1577, 915, 1650, 940, 500, 1186, 2201, 1350, 1~
credit_data %>% count(Status)

# trazer do servidor para o R (memória do computador local)
credit_data <- credit_data %>% collect()

PASSO 1) BASE TREINO/TESTE

set.seed(1)
credit_initial_split <- initial_split(credit_data, strata = "Status", prop = 0.75)

credit_train <- training(credit_initial_split)
credit_test  <- testing(credit_initial_split)

PASSO 2) EXPLORAR A BASE

skimr::skim(credit_train)
-- Data Summary ------------------------
                           Values      
Name                       credit_train
Number of rows             3340        
Number of columns          14          
_______________________                
Column type frequency:                 
  factor                   5           
  numeric                  9           
________________________               
Group variables            None        

-- Variable type: factor ---------------------------------------------------------------------------------------------------
# A tibble: 5 x 6
  skim_variable n_missing complete_rate ordered n_unique top_counts                             
* <chr>             <int>         <dbl> <lgl>      <int> <chr>                                  
1 Status                0         1     FALSE          2 goo: 2400, bad: 940                    
2 Home                  5         0.999 FALSE          6 own: 1604, ren: 728, par: 572, oth: 236
3 Marital               1         1.00  FALSE          5 mar: 2443, sin: 714, sep: 102, wid: 50 
4 Records               0         1     FALSE          2 no: 2756, yes: 584                     
5 Job                   2         0.999 FALSE          4 fix: 2113, fre: 754, par: 339, oth: 132

-- Variable type: numeric --------------------------------------------------------------------------------------------------
# A tibble: 9 x 11
  skim_variable n_missing complete_rate    mean       sd    p0   p25   p50   p75   p100 hist 
* <chr>             <int>         <dbl>   <dbl>    <dbl> <dbl> <dbl> <dbl> <dbl>  <dbl> <chr>
1 Seniority             0         1        8.05     8.24     0     2     5   12      47 ▇▃▁▁▁
2 Time                  0         1       46.1     14.8      6    36    48   60      60 ▁▂▅▃▇
3 Age                   0         1       37.3     11.0     18    28    36   45      68 ▆▇▆▃▁
4 Expenses              0         1       55.6     19.4     35    35    51   72     180 ▇▃▁▁▁
5 Income              285         0.915  141.      80.8      6    90   125  170     959 ▇▂▁▁▁
6 Assets               35         0.990 5611.   12523.       0     0  3100 6000  300000 ▇▁▁▁▁
7 Debt                 14         0.996  336.    1259.       0     0     0    0   30000 ▇▁▁▁▁
8 Amount                0         1     1024.     468.     100   700  1000 1300    5000 ▇▆▁▁▁
9 Price                 0         1     1448.     619.     105  1104  1398 1675.  11140 ▇▁▁▁▁
visdat::vis_miss(credit_train)

credit_train %>% 
  select(where(is.numeric)) %>% 
  cor(use = "p") %>% 
  corrplot::corrplot()

credit_train %>% 
  select(where(is.numeric), Status) %>%
  ggpairs(aes(colour = Status))

contagens %>%
  ggplot(aes(y = valor, x = n, fill = Status)) +
  geom_col(position = "fill") +
  geom_label(aes(label = n), position = position_fill(vjust = 0.5)) +
  facet_wrap(~variavel, scales = "free_y") +
  ggtitle("Status vs. Variáveis Categóricas")

credit_train %>% 
  select(c(where(is.numeric), Status)) %>%
  pivot_longer(-Status, names_to = "variavel", values_to = "valor") %>%
  ggplot(aes(y = Status, x = valor, fill = Status)) +
  geom_boxplot() +
  facet_wrap(~variavel, scales = "free_x") +
  ggtitle("Status vs. Variáveis Numéricas")

credit_train %>% 
  select(c(where(is.numeric), Status)) %>%
  pivot_longer(-Status, names_to = "variavel", values_to = "valor") %>%
  ggplot(aes(y = Status, x = valor, fill = Status)) +
  geom_boxplot() +
  facet_wrap(~variavel, scales = "free_x") +
  scale_x_log10() +
  ggtitle("Status vs. Variáveis Numéricas NA ESCALA LOG")

credit_train %>% 
  select(c(where(is.numeric), Status)) %>%
  pivot_longer(-Status, names_to = "variavel", values_to = "valor") %>%
  ggplot(aes(x = valor, colour = Status)) +
  stat_ecdf() +
  facet_wrap(~variavel, scales = "free_x") +
  labs(title = "Status vs. Variáveis Numéricas",
       subtitle = "Distribuição Acumulada")

PASSO 3) DATAPREP

credit_recipe
Data Recipe

Inputs:

Operations:

Zero variance filter on all_predictors()
Variable mutation for Home, Job, Marital, Assets, Income
Bagged tree imputation for Debt
Centering and scaling for all_numeric()
Novel factor level assignment for all_nominal_predictors()
# criando a base preparada
credit_preparada <- bake(prep(credit_recipe), new_data = NULL)
# olhando a base preparada
visdat::vis_miss(credit_preparada)

credit_preparada %>% 
  select(c(where(is.factor), Status)) %>%
  pivot_longer(-Status, names_to = "variavel", values_to = "valor") %>%
  count(Status, variavel, valor) %>%
  ggplot(aes(y = valor, x = n, fill = Status)) +
  geom_col(position = "fill") +
  geom_label(aes(label = n), position = position_fill(vjust = 0.5)) +
  facet_wrap(~variavel, scales = "free_y", ncol = 3) +
  ggtitle("Status vs. Variáveis Categóricas")

grafico_de_barras_das_vars_continuas(credit_preparada)

# finalizando a receita com dummies
credit_recipe <- credit_recipe %>%
  step_dummy(all_nominal_predictors())

PASSO 4) MODELO

# Definição de 
# a) a f(x): logistc_reg()
# b) modo (natureza da var resp): classification
# c) hiperparametros que queremos tunar: penalty = tune()
# d) hiperparametros que não queremos tunar: mixture = 1 # LASSO
# e) o motor que queremos usar: glmnet
credit_lr_model <- logistic_reg(penalty = tune(), mixture = 1) %>%
  set_mode("classification") %>%
  set_engine("glmnet")

# workflow
credit_wf <- workflow() %>% add_model(credit_lr_model) %>% add_recipe(credit_recipe)

PASSO 5) TUNAGEM DE HIPERPARÂMETROS

# a) bases de reamostragem para validação: vfold_cv()
# b) (opcional) grade de parâmetros: parameters() %>% update() %>% grid_regular()
# c) tune_grid(y ~ x + ...)
# d) escolha das métricas (rmse, roc_auc, etc)
# d) collect_metrics() ou autoplot() para ver o resultado
credit_resamples <- vfold_cv(credit_train, v = 5)

credit_lr_tune_grid <- tune_grid(
  credit_wf,
  resamples = credit_resamples,
  grid = 40,
  metrics = metric_set(
    accuracy, 
    roc_auc
    # kap, # KAPPA 
    # precision, 
    # recall, 
    # f_meas, 
    # mn_log_loss #binary cross entropy
  )
)

autoplot(credit_lr_tune_grid)

show_best(credit_lr_tune_grid)
collect_metrics(credit_lr_tune_grid)

PASSO 6) DESEMPENHO DO MODELO FINAL

# a) extrai melhor modelo com select_best()
# b) finaliza o modelo inicial com finalize_model()
# c) ajusta o modelo final com todos os dados de treino (bases de validação já era)
credit_lr_best_params <- select_best(credit_lr_tune_grid, "roc_auc")
credit_wf <- credit_wf %>% finalize_workflow(credit_lr_best_params)

credit_lr_last_fit <- last_fit(
  credit_wf,
  credit_initial_split
)

credit_lr_last_fit
# Resampling results
# Manual resampling 
# métricas de desempenho
collect_metrics(credit_lr_last_fit)
# base de teste com as predições
collect_predictions(credit_lr_last_fit)

Variáveis importantes

# extrai o modelo do objeto de ajuste
credit_lr_last_fit_model <- pull_workflow_fit(credit_lr_last_fit$.workflow[[1]])

# tabela de betas
vi(credit_lr_last_fit_model)
# gráfico de Variable Importances
vip(credit_lr_last_fit_model)

# outra maneira de pegar os betas: broom::tidy()
betas <- broom::tidy(credit_lr_last_fit_model)
betas %>% arrange(desc(abs(estimate)))

Métricas de desempenho

collect_metrics(credit_lr_last_fit)

credit_test_preds <- collect_predictions(credit_lr_last_fit)

# roc
credit_roc_curve <- credit_test_preds %>% roc_curve(Status, .pred_bad)
autoplot(credit_roc_curve)

# confusion matrix
credit_test_preds %>%
  mutate(
    Status_class = factor(if_else(.pred_bad > 0.6, "bad", "good"))
  ) %>%
  conf_mat(Status, Status_class)
          Truth
Prediction bad good
      bad  123   35
      good 191  765

Gráficos extras

# risco por faixa de score (multiplas notas de corte)]
percentis = 15
credit_test_preds %>%
  mutate(
    score =  factor(ntile(.pred_bad, percentis))
  ) %>%
  count(score, Status) %>%
  ggplot(aes(y = score, x = n, fill = Status)) +
  geom_col(position = "fill") +
  geom_label(aes(label = n), position = "fill")

# gráfico sobre os da classe "bad"
credit_test_preds %>%
  mutate(
    score = factor(ntile(.pred_bad, percentis))
  ) %>%
  filter(Status == "bad") %>%
  group_by(score) %>%
  summarise(
    n = n(),
    media = mean(.pred_bad)
  ) %>%
  mutate(p = n/sum(n)) %>%
  ggplot(aes(x = p, y = score)) +
  geom_col() +
  geom_label(aes(label = scales::percent(p)), hjust = 1.1) +
  geom_vline(xintercept = 1/percentis, colour = "red", linetype = "dashed", size = 1)

PASSO 7) MODELO FINAL

Ajuste final

credit_final_lr_model <- fit(credit_wf, credit_data)

Variáveis do modelo final

# extrai o modelo do objeto de ajuste
credit_final_lr_fit <- credit_final_lr_model$fit$fit

# tabela de betas
vi(credit_final_lr_fit)
vip(credit_final_lr_fit)


# salva tudo no computador
write_rds(credit_final_lr_model, "credit_final_lr_model.rds")
write_rds(credit_lr_last_fit, "credit_lr_last_fit.rds")

predições

# predições
dados_novos <- testing(credit_initial_split)
predict(credit_final_lr_model, new_data = dados_novos, type = "prob")

# colocar no BD
dados_novos <- bind_cols(
  dados_novos,
  predict(credit_final_lr_model, new_data = dados_novos, type = "prob")
)

# salvar no excel
writexl::write_xlsx(dados_novos, "dados_novos.xlsx")

# salvar no SQL
copy_to(con, dados_novos, overwrite = TRUE, temporary = FALSE)
dbListTables(con)
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIGV2YWwgPSBUUlVFLCBoaWRlID0gVFJVRSkKYGBgCgojIFJlZmVyw6puY2lhcwoKLSBbUiBmb3IgRGF0YSBTY2llbmNlXShodHRwczovL3I0ZHMuaGFkLmNvLm56LykKLSBbVGlkeSBNb2RlbGluZyBXaXRoIFJdKGh0dHBzOi8vd3d3LnRtd3Iub3JnLykKLSBbcGFjb3RlIHtzbGlkZXJ9XShodHRwczovL2RhdmlzdmF1Z2hhbi5naXRodWIuaW8vc2xpZGVyLykKLSBbRmVhdHVyZSBFbmdpbmVlcmluZyBBbmQgU2VsZWN0aW9uXShodHRwOi8vd3d3LmZlYXQuZW5naW5lZXJpbmcvKQotIFtJbnRyb2R1Y3Rpb24gVG8gU3RhdGlzdGljYWwgTGVhcm5pbmddKGh0dHBzOi8vc3RhdGljMS5zcXVhcmVzcGFjZS5jb20vc3RhdGljLzVmZjJhZGJlM2ZlNGZlMzNkYjkwMjgxMi90LzYwNjJhMDgzYWNiZmU4MmM3MTk1YjI3ZC8xNjE3MDc2NDA0NTYwL0lTTFIlMkJTZXZlbnRoJTJCUHJpbnRpbmcucGRmKQotIFtNYXRlcmlhbCBkbyBDdXJzbyBkZSBNYWNoaW5lIExlYXJuaW5nIGRhIEN1cnNvLVJdKGh0dHBzOi8vZ2l0aHViLmNvbS9jdXJzby1yLzIwMjEwNC1pbnRyby1tbCkKCmBgYHtyfQojIHBhY290ZXMgLS0tLS0tLS0tLS0tLS0tCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KEdHYWxseSkKYGBgCgojIENvbmV4w6NvIFIgZSBTUUwKCmBgYHtyfQojIEV4ZW1wbG8gZGUgY29uZXjDo28gY29tIHVtIE1hcmlhREIgKHRhbWLDqW0gY29uaGVjaWRvIGNvbW8gTXlTUUwpIHJlbW90by4KY29uX21hcmlhZGIgPC0gREJJOjpkYkNvbm5lY3QoCiAgUk1hcmlhREI6Ok1hcmlhREIoKSwgCiAgaG9zdCA9ICJyZWxhdGlvbmFsLmZpdC5jdnV0LmN6IiwgCiAgcG9ydCA9IDMzMDYsIAogIHVzZXJuYW1lID0gImd1ZXN0IiwgCiAgcGFzc3dvcmQgPSAicmVsYXRpb25hbCIsCiAgZGJuYW1lID0gImZpbmFuY2lhbCIKKQpEQkk6OmRiTGlzdFRhYmxlcyhjb25fbWFyaWFkYikKYGBgCgpgYGB7cn0KIyBDb25leGFvIGNvbSBTUUxpdGUgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KY29uIDwtIERCSTo6ZGJDb25uZWN0KFJTUUxpdGU6OlNRTGl0ZSgpLCAiZGFkb3NfY29uc3VsdG9yaWEuZGIiKQojIEFjZXNzYW5kbyBvIFNRTCBhIHBhcnRpciBkbyBSIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIGNvbXB1dGHDp8OjbyDDqSBmZWl0YSBsw6Egbm8gc2Vydmlkb3IuCnRibChjb24sICJpbmRpY2Fkb3JlcyIpICU+JQogIGNvdW50KGlkKQojIGTDoSBwcmEgY29uc3VsdGFyIGEgcXVlcnkgZGUgU1FMIHF1ZSByb2RvdSBsw6Egbm8gc2Vydmlkb3IuCnRibChjb24sICJpbmRpY2Fkb3JlcyIpICU+JQogIGNvdW50KGlkKSAlPiUKICBzaG93X3F1ZXJ5KCkKIyBwYXNzYW5kbyBkbyBTUUwgcHJvIFIgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBjb2xsZWN0KCkgZmF6IG8gZG93bmxvYWQgcHJvIGNvbXB1dGFkbyBsb2NhbCAobsOjbyBlc3RhbW9zIG1haXMgdXNhbmRvIG8gc2Vydmlkb3IpLgppbmRpY2Fkb3JlcyA8LSB0YmwoY29uLCAiaW5kaWNhZG9yZXMiKSAlPiUgY29sbGVjdCgpCiMgUFM6IG8gU1FMaXRlIG7Do28gdGVtIGZvcm1hdG8gZGUgZGF0YXMsIGVudMOjbyB0ZW0gcXVlIHRyYW5zZm9ybWFyIGVtIGRhdGEgcXVhbmRvIHZlbSBwcm8gUi4gRXNzZSBwcm9ibGVtYSBuw6NvIHRlbSBubyBNeVNRTCBvdSBubyBTUUwgU2VydmVyLgppbmRpY2Fkb3JlcyA8LSBpbmRpY2Fkb3JlcyAlPiUKICBtdXRhdGUoCiAgICBkYXRhID0gYXMuRGF0ZShkYXRhKQogICkKYGBgCgoKIyBFeGVtcGxvIGRlIG1hbmlwdWxhw6fDo28gZGUgZGFkb3MKCipJbmZvcm1hw6fDo28gMSogLSBTb2JyZSBvcyBJRHMsIG8gY2xpZW50ZSBpbmZvcm1vdSBxdWUgZGV2ZXJpYSB0ZXIgYXBlbmFzIHVtYSBsaW5oYSBwYXJhIGNhZGEgdHJpbmNhIChpZC1hbm8tbWVzKS4KUG9yIGNvbnRhIGRlIHVtYSBpbmNvbnNpc3TDqm5jaWEsIHBvZGVyaWEgYWNvbnRlY2VyIGRlIHZpcmVtIGR1YXMgb3UgbWFpcyBsaW5oYXMgcGFyYSBvIG1lc21hIHRyaW5jYSAoaWQtYW5vLW1lcykuCk8gY29ycmV0byDDqSB0ZXIgYXBlbmFzIHVtYSBsaW5oYSBhcGVuYXMuIEVsZXMgZGlzc2VyYW0gcXVlIGEgbGluaGEgY29tIG8gbWFpb3IgdmFsb3IgZGUgYWdlbmRhbWVudG8gdGVtIG1haXMgY2hhbmNlCmRlIHNlciBhIGNvcnJldGEuCgoKYGBge3J9CiMgU29sdcOnw6NvOiBhcnJhbmdlKCkgKyBkaXN0aW5jdCgpCmluZGljYWRvcmVzIDwtIGluZGljYWRvcmVzICU+JQogIGRwbHlyOjphcnJhbmdlKGRlc2MoYWdlbmRhbWVudG8pKSAlPiUKICBkcGx5cjo6ZGlzdGluY3QoaWQsIGFubywgbWVzLCAua2VlcF9hbGwgPSBUUlVFKQpgYGAKCgoqSW5mb3JtYcOnw6NvIDIqIC0gU29icmUgYXMgc8OpcmllcyBtZW5zYWlzLCBvIGNsaWVudGUgaW5mb3Jtb3UgcXVlOgoKMSkgSURzIHBvZGVtIHRlciBpbsOtY2lvIGUgZmltIGRpc3RpbnRvcy4KMikgQSBzw6lyaWUgZGUgbWVzZXMgZGUgdW0gSUQgbsOjbyB0ZXZlIHRlciBtw6pzIGZhbHRhbnRlIGVudHJlIHNldSBpbsOtY2lvIGUgc2V1IGZpbSwKICAgcG9yw6ltLCBlbSB2aXJ0dWRlIGRlIHByb2JsZW1hcyB0w6ljbmljb3MsIHBvZGUgaGF2ZXIgcGVyZGEgZGUgaW5mb3JtYcOnw6NvIG5vIG1laW8KICAgZG8gcHJvY2Vzc28uIEFzc2ltLCBuZXNzZXMgY2Fzb3MsIG9yaWVudGEtc2Ugc3Vic3RpdHVpciBvIHZhbG9yIGZhbHRhbnRlIHBlbG8KICAgdmFsb3IgZG8gbcOqcyBhbnRlcmlvci4KCgoKYGBge3J9CiMgT2xoYW5kbyBvIHByb2JsZW1hIGRvcyBtZXNlcyBmYWx0YW50ZXMKaW5kaWNhZG9yZXMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZGF0YSwgeSA9IGlkLCBjb2xvdXIgPSBpZCkpICsKICBnZW9tX3BvaW50KHNpemUgPSA1KSAKIyBTb2x1w6fDo286IHtwYWRyfSArIHt0aWR5cn0gKGV4ZW1wbG8gY29tIG8gaWQgOTcwKQppbmRpY2Fkb3JlcyAlPiUKICBkcGx5cjo6ZmlsdGVyKGlkID09IDk3MCkgJT4lCiAgZHBseXI6OmFycmFuZ2UoZGF0YSkgJT4lCiAgcGFkcjo6cGFkKGludGVydmFsID0gIm1vbnRoIiwgZ3JvdXAgPSAiaWQiKQppbmRpY2Fkb3Jlc19jb21fcGFkIDwtIGluZGljYWRvcmVzICAlPiUKICBwYWRyOjpwYWQoaW50ZXJ2YWwgPSAibW9udGgiLCBncm91cCA9ICJpZCIpICAKIyBwYWRyOjpwYWQoKSBjb25zZXJ0b3UKaW5kaWNhZG9yZXNfY29tX3BhZCAlPiUKICBnZ3Bsb3QoYWVzKHggPSBkYXRhLCB5ID0gaWQsIGNvbG91ciA9IGlkKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDUpIAojIGFnb3JhIHRlbSBxdWUgcHJlZW5jaGVyIG9zIE5BcyBjb20gZmlsbC4KaW5kaWNhZG9yZXNfY29tX3BhZCA8LSBpbmRpY2Fkb3Jlc19jb21fcGFkICU+JQogIGRwbHlyOjphcnJhbmdlKGlkLCBkYXRhKSAlPiUKICB0aWR5cjo6ZmlsbChhZ2VuZGFtZW50bzpjaWRhZGUpICU+JQogIGRwbHlyOjptdXRhdGUoCiAgICAjIG1lcyBlIGFubyBuw6NvIGTDoSBwcmEgcHJlZW5jaGVyIGNvbSBmaWxsIGRpcmV0YW1lbnRlCiAgICBtZXMgPSBhcy5jaGFyYWN0ZXIobHVicmlkYXRlOjptb250aChkYXRhKSksCiAgICBhbm8gPSBhcy5jaGFyYWN0ZXIobHVicmlkYXRlOjp5ZWFyKGRhdGEpKQogICkKYGBgCgoKIyBFeGVtcGxvcyBkZSBDcmlhw6fDo28gZGUgRmVhdHVyZXMgamFuZWxhZGFzCgpgYGB7cn0KICMgZmVhdHVyZXMgbm8gdGVtcG8gKGV4ZW1wbG8pIC0tLS0tLS0tLS0tLS0tLS0tLS0tLQp0ZXN0ZSA8LSBpbmRpY2Fkb3Jlc19jb21fcGFkICU+JSAKICBmaWx0ZXIoaWQgJWluJSBjKDQwNiwgNDIwKSwgZGF0YSAlPiUgYmV0d2Vlbihhcy5EYXRlKCIyMDE5LTAxLTAxIiksIGFzLkRhdGUoIjIwMTktMDQtMDEiKSkpICU+JQogIHNlbGVjdChpZCwgZGF0YSkKIyB0cmFuc2Zvcm1hY29lcwp0ZXN0ZSAlPiUKICBhcnJhbmdlKGlkLCBkYXRhKSAlPiUKICBncm91cF9ieShpZCkgJT4lCiAgbXV0YXRlKAogICAgeCA9IDE6bigpLAogICAgYSA9IGN1bXN1bSh4KSwKICAgIGIgPSBsYWcoeCksCiAgICBjID0gbGFnKHgsIG4gPSAyKSwKICAgIGQgPSBsZWFkKHgpLAogICAgZSA9IHNsaWRlcjo6c2xpZGVfZGJsKHgsIG1lYW4sIC5iZWZvcmUgPSAxLCAuYWZ0ZXIgPSAwKSwKICAgIGYgPSBzbGlkZXI6OnNsaWRlX2RibCh4LCBtZWFuLCAuYmVmb3JlID0gMCwgLmFmdGVyID0gMCksCiAgICBoID0geC9sYWcoeCkKICApCmBgYAoKCiMgRmx1eG8gZGUgVHJhYmFsaG8gZG8gVGlkeW1vZGVscwoKIyMgUEFTU08gMCkgQ0FSUkVHQVIgQVMgQkFTRVMKCmBgYHtyfQojIGh0dHBzOi8vZ2l0aHViLmNvbS9nYXN0b25zdGF0L0NyZWRpdFNjb3JpbmcgCiMgaHR0cDovL2JpdC5seS8ya2tCRnJrCmxpYnJhcnkobW9kZWxkYXRhKQpkYXRhKGNyZWRpdF9kYXRhKQpnbGltcHNlKGNyZWRpdF9kYXRhKSAjIEdlcm1hbiBSaXNrCgpjcmVkaXRfZGF0YSAlPiUgY291bnQoU3RhdHVzKQoKIyB0cmF6ZXIgZG8gc2Vydmlkb3IgcGFyYSBvIFIgKG1lbcOzcmlhIGRvIGNvbXB1dGFkb3IgbG9jYWwpCmNyZWRpdF9kYXRhIDwtIGNyZWRpdF9kYXRhICU+JSBjb2xsZWN0KCkKYGBgCgojIyBQQVNTTyAxKSBCQVNFIFRSRUlOTy9URVNURQoKYGBge3J9CnNldC5zZWVkKDEpCmNyZWRpdF9pbml0aWFsX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQoY3JlZGl0X2RhdGEsIHN0cmF0YSA9ICJTdGF0dXMiLCBwcm9wID0gMC43NSkKCmNyZWRpdF90cmFpbiA8LSB0cmFpbmluZyhjcmVkaXRfaW5pdGlhbF9zcGxpdCkKY3JlZGl0X3Rlc3QgIDwtIHRlc3RpbmcoY3JlZGl0X2luaXRpYWxfc3BsaXQpCmBgYAoKIyMgUEFTU08gMikgRVhQTE9SQVIgQSBCQVNFCgpgYGB7cn0Kc2tpbXI6OnNraW0oY3JlZGl0X3RyYWluKQpgYGAKCmBgYHtyfQp2aXNkYXQ6OnZpc19taXNzKGNyZWRpdF90cmFpbikKYGBgCgpgYGB7cn0KY3JlZGl0X3RyYWluICU+JSAKICBzZWxlY3Qod2hlcmUoaXMubnVtZXJpYykpICU+JSAKICBjb3IodXNlID0gInAiKSAlPiUgCiAgY29ycnBsb3Q6OmNvcnJwbG90KCkKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD0xMiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KY3JlZGl0X3RyYWluICU+JSAKICBzZWxlY3Qod2hlcmUoaXMubnVtZXJpYyksIFN0YXR1cykgJT4lCiAgZ2dwYWlycyhhZXMoY29sb3VyID0gU3RhdHVzKSkKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD04fQpjb250YWdlbnMgPC0gY3JlZGl0X3RyYWluICU+JSAKICBzZWxlY3QoYyh3aGVyZShpcy5mYWN0b3IpLCBTdGF0dXMpKSAlPiUKICBwaXZvdF9sb25nZXIoLVN0YXR1cywgbmFtZXNfdG8gPSAidmFyaWF2ZWwiLCB2YWx1ZXNfdG8gPSAidmFsb3IiKSAlPiUKICBjb3VudChTdGF0dXMsIHZhcmlhdmVsLCB2YWxvcikKCiMgdGFiZWxhCmNvbnRhZ2VucyAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gU3RhdHVzLCB2YWx1ZXNfZnJvbSA9IG4pCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQ9OH0KY29udGFnZW5zICU+JQogIGdncGxvdChhZXMoeSA9IHZhbG9yLCB4ID0gbiwgZmlsbCA9IFN0YXR1cykpICsKICBnZW9tX2NvbChwb3NpdGlvbiA9ICJmaWxsIikgKwogIGdlb21fbGFiZWwoYWVzKGxhYmVsID0gbiksIHBvc2l0aW9uID0gcG9zaXRpb25fZmlsbCh2anVzdCA9IDAuNSkpICsKICBmYWNldF93cmFwKH52YXJpYXZlbCwgc2NhbGVzID0gImZyZWVfeSIpICsKICBnZ3RpdGxlKCJTdGF0dXMgdnMuIFZhcmnDoXZlaXMgQ2F0ZWfDs3JpY2FzIikKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD04fQpjcmVkaXRfdHJhaW4gJT4lIAogIHNlbGVjdChjKHdoZXJlKGlzLm51bWVyaWMpLCBTdGF0dXMpKSAlPiUKICBwaXZvdF9sb25nZXIoLVN0YXR1cywgbmFtZXNfdG8gPSAidmFyaWF2ZWwiLCB2YWx1ZXNfdG8gPSAidmFsb3IiKSAlPiUKICBnZ3Bsb3QoYWVzKHkgPSBTdGF0dXMsIHggPSB2YWxvciwgZmlsbCA9IFN0YXR1cykpICsKICBnZW9tX2JveHBsb3QoKSArCiAgZmFjZXRfd3JhcCh+dmFyaWF2ZWwsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgZ2d0aXRsZSgiU3RhdHVzIHZzLiBWYXJpw6F2ZWlzIE51bcOpcmljYXMiKQpgYGAKCmBgYHtyLCBmaWcuaGVpZ2h0PTh9CmNyZWRpdF90cmFpbiAlPiUgCiAgc2VsZWN0KGMod2hlcmUoaXMubnVtZXJpYyksIFN0YXR1cykpICU+JQogIHBpdm90X2xvbmdlcigtU3RhdHVzLCBuYW1lc190byA9ICJ2YXJpYXZlbCIsIHZhbHVlc190byA9ICJ2YWxvciIpICU+JQogIGdncGxvdChhZXMoeSA9IFN0YXR1cywgeCA9IHZhbG9yLCBmaWxsID0gU3RhdHVzKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBmYWNldF93cmFwKH52YXJpYXZlbCwgc2NhbGVzID0gImZyZWVfeCIpICsKICBzY2FsZV94X2xvZzEwKCkgKwogIGdndGl0bGUoIlN0YXR1cyB2cy4gVmFyacOhdmVpcyBOdW3DqXJpY2FzIE5BIEVTQ0FMQSBMT0ciKQpgYGAKCmBgYHtyLCBmaWcuaGVpZ2h0PTh9CmNyZWRpdF90cmFpbiAlPiUgCiAgc2VsZWN0KGMod2hlcmUoaXMubnVtZXJpYyksIFN0YXR1cykpICU+JQogIHBpdm90X2xvbmdlcigtU3RhdHVzLCBuYW1lc190byA9ICJ2YXJpYXZlbCIsIHZhbHVlc190byA9ICJ2YWxvciIpICU+JQogIGdncGxvdChhZXMoeCA9IHZhbG9yLCBjb2xvdXIgPSBTdGF0dXMpKSArCiAgc3RhdF9lY2RmKCkgKwogIGZhY2V0X3dyYXAofnZhcmlhdmVsLCBzY2FsZXMgPSAiZnJlZV94IikgKwogIGxhYnModGl0bGUgPSAiU3RhdHVzIHZzLiBWYXJpw6F2ZWlzIE51bcOpcmljYXMiLAogICAgICAgc3VidGl0bGUgPSAiRGlzdHJpYnVpw6fDo28gQWN1bXVsYWRhIikKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD0xM30KZ3JhZmljb19kZV9iYXJyYXNfZGFzX3ZhcnNfY29udGludWFzIDwtIGZ1bmN0aW9uKGRhZG9zLCBiYXJyYXMgPSAxMCkgewogIGRhZG9zICU+JSAKICAgIHNlbGVjdChjKHdoZXJlKGlzLm51bWVyaWMpLCBTdGF0dXMpKSAlPiUKICAgIHBpdm90X2xvbmdlcigtU3RhdHVzLCBuYW1lc190byA9ICJ2YXJpYXZlbCIsIHZhbHVlc190byA9ICJ2YWxvciIpICU+JQogICAgZHBseXI6Omdyb3VwX2J5KHZhcmlhdmVsKSAlPiUKICAgIGRwbHlyOjptdXRhdGUoCiAgICAgIHZhbG9yID0gZmFjdG9yKGRwbHlyOjpudGlsZSh2YWxvciwgYmFycmFzKSwgbGV2ZWxzID0gMTpiYXJyYXMpCiAgICApICU+JQogICAgZHBseXI6OmNvdW50KFN0YXR1cywgdmFyaWF2ZWwsIHZhbG9yKSAlPiUKICAgIGdncGxvdChhZXMoeSA9ICh2YWxvciksIHggPSBuLCBmaWxsID0gU3RhdHVzKSkgKwogICAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZmlsbCIpICsKICAgIGdlb21fbGFiZWwoYWVzKGxhYmVsID0gbiksIHBvc2l0aW9uID0gcG9zaXRpb25fZmlsbCh2anVzdCA9IDAuNSkpICsKICAgIGZhY2V0X3dyYXAofnZhcmlhdmVsLCBzY2FsZXMgPSAiZnJlZV95IiwgbmNvbCA9IDMpICsKICAgIGdndGl0bGUoIlN0YXR1cyB2cy4gVmFyacOhdmVpcyBDYXRlZ8OzcmljYXMiKQp9CgpncmFmaWNvX2RlX2JhcnJhc19kYXNfdmFyc19jb250aW51YXMoY3JlZGl0X3RyYWluLCBiYXJyYXMgPSAxMikKYGBgCgojIyBQQVNTTyAzKSBEQVRBUFJFUAoKYGBge3J9CiMgY3JpYW5kbyBhIHJlY2VpdGEKY3JlZGl0X3JlY2lwZSA8LSByZWNpcGUoU3RhdHVzIH4gLiwgZGF0YSA9IGNyZWRpdF90cmFpbikgJT4lCiAgc3RlcF96dihhbGxfcHJlZGljdG9ycygpKSAlPiUKICBzdGVwX211dGF0ZSgKICAgIEhvbWUgPSBpZmVsc2UoaXMubmEoSG9tZSksICJvdGhlciIsIGFzLmNoYXJhY3RlcihIb21lKSksCiAgICBKb2IgPSBpZmVsc2UoaXMubmEoSm9iKSwgInBhcnRpbWUiLCBhcy5jaGFyYWN0ZXIoSm9iKSksCiAgICBNYXJpdGFsID0gaWZlbHNlKGlzLm5hKE1hcml0YWwpLCAibWFycmllZCIsYXMuY2hhcmFjdGVyKE1hcml0YWwpKSwKICAgIEFzc2V0cyA9IGlmZWxzZShpcy5uYShBc3NldHMpLCBtaW4oQXNzZXRzLCBuYS5ybSA9IFRSVUUpLCBBc3NldHMpLAogICAgSW5jb21lID0gaWZlbHNlKGlzLm5hKEluY29tZSksIG1pbihJbmNvbWUsIG5hLnJtID0gVFJVRSksIEluY29tZSksCiAgKSAlPiUKICBzdGVwX2ltcHV0ZV9iYWcoRGVidCwgaW1wdXRlX3dpdGggPSBpbXBfdmFycyhJbmNvbWUsIFByaWNlLCBBbW91bnQpKSAlPiUKICBzdGVwX25vcm1hbGl6ZShhbGxfbnVtZXJpYygpKSAlPiUKICBzdGVwX25vdmVsKGFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkKCmNyZWRpdF9yZWNpcGUKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD0xNX0KIyBjcmlhbmRvIGEgYmFzZSBwcmVwYXJhZGEKY3JlZGl0X3ByZXBhcmFkYSA8LSBiYWtlKHByZXAoY3JlZGl0X3JlY2lwZSksIG5ld19kYXRhID0gTlVMTCkKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD04fQojIG9saGFuZG8gYSBiYXNlIHByZXBhcmFkYQp2aXNkYXQ6OnZpc19taXNzKGNyZWRpdF9wcmVwYXJhZGEpCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQ9MTN9CmNyZWRpdF9wcmVwYXJhZGEgJT4lIAogIHNlbGVjdChjKHdoZXJlKGlzLmZhY3RvciksIFN0YXR1cykpICU+JQogIHBpdm90X2xvbmdlcigtU3RhdHVzLCBuYW1lc190byA9ICJ2YXJpYXZlbCIsIHZhbHVlc190byA9ICJ2YWxvciIpICU+JQogIGNvdW50KFN0YXR1cywgdmFyaWF2ZWwsIHZhbG9yKSAlPiUKICBnZ3Bsb3QoYWVzKHkgPSB2YWxvciwgeCA9IG4sIGZpbGwgPSBTdGF0dXMpKSArCiAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZmlsbCIpICsKICBnZW9tX2xhYmVsKGFlcyhsYWJlbCA9IG4pLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2ZpbGwodmp1c3QgPSAwLjUpKSArCiAgZmFjZXRfd3JhcCh+dmFyaWF2ZWwsIHNjYWxlcyA9ICJmcmVlX3kiLCBuY29sID0gMykgKwogIGdndGl0bGUoIlN0YXR1cyB2cy4gVmFyacOhdmVpcyBDYXRlZ8OzcmljYXMiKQpgYGAKCmBgYHtyLCBmaWcuaGVpZ2h0PTEwfQpncmFmaWNvX2RlX2JhcnJhc19kYXNfdmFyc19jb250aW51YXMoY3JlZGl0X3ByZXBhcmFkYSkKYGBgCgpgYGB7cn0KIyBmaW5hbGl6YW5kbyBhIHJlY2VpdGEgY29tIGR1bW1pZXMKY3JlZGl0X3JlY2lwZSA8LSBjcmVkaXRfcmVjaXBlICU+JQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKQpgYGAKCiMjIFBBU1NPIDQpIE1PREVMTwoKYGBge3J9CiMgRGVmaW5pw6fDo28gZGUgCiMgYSkgYSBmKHgpOiBsb2dpc3RjX3JlZygpCiMgYikgbW9kbyAobmF0dXJlemEgZGEgdmFyIHJlc3ApOiBjbGFzc2lmaWNhdGlvbgojIGMpIGhpcGVycGFyYW1ldHJvcyBxdWUgcXVlcmVtb3MgdHVuYXI6IHBlbmFsdHkgPSB0dW5lKCkKIyBkKSBoaXBlcnBhcmFtZXRyb3MgcXVlIG7Do28gcXVlcmVtb3MgdHVuYXI6IG1peHR1cmUgPSAxICMgTEFTU08KIyBlKSBvIG1vdG9yIHF1ZSBxdWVyZW1vcyB1c2FyOiBnbG1uZXQKY3JlZGl0X2xyX21vZGVsIDwtIGxvZ2lzdGljX3JlZyhwZW5hbHR5ID0gdHVuZSgpLCBtaXh0dXJlID0gMSkgJT4lCiAgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIikgJT4lCiAgc2V0X2VuZ2luZSgiZ2xtbmV0IikKCiMgd29ya2Zsb3cKY3JlZGl0X3dmIDwtIHdvcmtmbG93KCkgJT4lIGFkZF9tb2RlbChjcmVkaXRfbHJfbW9kZWwpICU+JSBhZGRfcmVjaXBlKGNyZWRpdF9yZWNpcGUpCmBgYAoKIyMgUEFTU08gNSkgVFVOQUdFTSBERSBISVBFUlBBUsOCTUVUUk9TCgpgYGB7cn0KIyBhKSBiYXNlcyBkZSByZWFtb3N0cmFnZW0gcGFyYSB2YWxpZGHDp8OjbzogdmZvbGRfY3YoKQojIGIpIChvcGNpb25hbCkgZ3JhZGUgZGUgcGFyw6JtZXRyb3M6IHBhcmFtZXRlcnMoKSAlPiUgdXBkYXRlKCkgJT4lIGdyaWRfcmVndWxhcigpCiMgYykgdHVuZV9ncmlkKHkgfiB4ICsgLi4uKQojIGQpIGVzY29saGEgZGFzIG3DqXRyaWNhcyAocm1zZSwgcm9jX2F1YywgZXRjKQojIGQpIGNvbGxlY3RfbWV0cmljcygpIG91IGF1dG9wbG90KCkgcGFyYSB2ZXIgbyByZXN1bHRhZG8KY3JlZGl0X3Jlc2FtcGxlcyA8LSB2Zm9sZF9jdihjcmVkaXRfdHJhaW4sIHYgPSA1KQoKY3JlZGl0X2xyX3R1bmVfZ3JpZCA8LSB0dW5lX2dyaWQoCiAgY3JlZGl0X3dmLAogIHJlc2FtcGxlcyA9IGNyZWRpdF9yZXNhbXBsZXMsCiAgZ3JpZCA9IDQwLAogIG1ldHJpY3MgPSBtZXRyaWNfc2V0KAogICAgYWNjdXJhY3ksIAogICAgcm9jX2F1YwogICAgIyBrYXAsICMgS0FQUEEgCiAgICAjIHByZWNpc2lvbiwgCiAgICAjIHJlY2FsbCwgCiAgICAjIGZfbWVhcywgCiAgICAjIG1uX2xvZ19sb3NzICNiaW5hcnkgY3Jvc3MgZW50cm9weQogICkKKQoKYXV0b3Bsb3QoY3JlZGl0X2xyX3R1bmVfZ3JpZCkKc2hvd19iZXN0KGNyZWRpdF9scl90dW5lX2dyaWQpCmNvbGxlY3RfbWV0cmljcyhjcmVkaXRfbHJfdHVuZV9ncmlkKQpgYGAKCiMjIFBBU1NPIDYpIERFU0VNUEVOSE8gRE8gTU9ERUxPIEZJTkFMCgpgYGB7cn0KIyBhKSBleHRyYWkgbWVsaG9yIG1vZGVsbyBjb20gc2VsZWN0X2Jlc3QoKQojIGIpIGZpbmFsaXphIG8gbW9kZWxvIGluaWNpYWwgY29tIGZpbmFsaXplX21vZGVsKCkKIyBjKSBhanVzdGEgbyBtb2RlbG8gZmluYWwgY29tIHRvZG9zIG9zIGRhZG9zIGRlIHRyZWlubyAoYmFzZXMgZGUgdmFsaWRhw6fDo28gasOhIGVyYSkKY3JlZGl0X2xyX2Jlc3RfcGFyYW1zIDwtIHNlbGVjdF9iZXN0KGNyZWRpdF9scl90dW5lX2dyaWQsICJyb2NfYXVjIikKY3JlZGl0X3dmIDwtIGNyZWRpdF93ZiAlPiUgZmluYWxpemVfd29ya2Zsb3coY3JlZGl0X2xyX2Jlc3RfcGFyYW1zKQoKY3JlZGl0X2xyX2xhc3RfZml0IDwtIGxhc3RfZml0KAogIGNyZWRpdF93ZiwKICBjcmVkaXRfaW5pdGlhbF9zcGxpdAopCgpjcmVkaXRfbHJfbGFzdF9maXQKYGBgCgpgYGB7cn0KIyBtw6l0cmljYXMgZGUgZGVzZW1wZW5obwpjb2xsZWN0X21ldHJpY3MoY3JlZGl0X2xyX2xhc3RfZml0KQpgYGAKCmBgYHtyfQojIGJhc2UgZGUgdGVzdGUgY29tIGFzIHByZWRpw6fDtWVzCmNvbGxlY3RfcHJlZGljdGlvbnMoY3JlZGl0X2xyX2xhc3RfZml0KQpgYGAKCiMjIyBWYXJpw6F2ZWlzIGltcG9ydGFudGVzCgpgYGB7cn0KIyBleHRyYWkgbyBtb2RlbG8gZG8gb2JqZXRvIGRlIGFqdXN0ZQpjcmVkaXRfbHJfbGFzdF9maXRfbW9kZWwgPC0gcHVsbF93b3JrZmxvd19maXQoY3JlZGl0X2xyX2xhc3RfZml0JC53b3JrZmxvd1tbMV1dKQoKIyB0YWJlbGEgZGUgYmV0YXMKdmkoY3JlZGl0X2xyX2xhc3RfZml0X21vZGVsKQpgYGAKCmBgYHtyfQojIGdyw6FmaWNvIGRlIFZhcmlhYmxlIEltcG9ydGFuY2VzCnZpcChjcmVkaXRfbHJfbGFzdF9maXRfbW9kZWwpCmBgYAoKYGBge3J9CiMgb3V0cmEgbWFuZWlyYSBkZSBwZWdhciBvcyBiZXRhczogYnJvb206OnRpZHkoKQpiZXRhcyA8LSBicm9vbTo6dGlkeShjcmVkaXRfbHJfbGFzdF9maXRfbW9kZWwpCmJldGFzICU+JSBhcnJhbmdlKGRlc2MoYWJzKGVzdGltYXRlKSkpCmBgYAoKIyMjIE3DqXRyaWNhcyBkZSBkZXNlbXBlbmhvCgpgYGB7cn0KY29sbGVjdF9tZXRyaWNzKGNyZWRpdF9scl9sYXN0X2ZpdCkKCmNyZWRpdF90ZXN0X3ByZWRzIDwtIGNvbGxlY3RfcHJlZGljdGlvbnMoY3JlZGl0X2xyX2xhc3RfZml0KQoKIyByb2MKY3JlZGl0X3JvY19jdXJ2ZSA8LSBjcmVkaXRfdGVzdF9wcmVkcyAlPiUgcm9jX2N1cnZlKFN0YXR1cywgLnByZWRfYmFkKQphdXRvcGxvdChjcmVkaXRfcm9jX2N1cnZlKQpgYGAKCmBgYHtyfQojIGNvbmZ1c2lvbiBtYXRyaXgKY3JlZGl0X3Rlc3RfcHJlZHMgJT4lCiAgbXV0YXRlKAogICAgU3RhdHVzX2NsYXNzID0gZmFjdG9yKGlmX2Vsc2UoLnByZWRfYmFkID4gMC42LCAiYmFkIiwgImdvb2QiKSkKICApICU+JQogIGNvbmZfbWF0KFN0YXR1cywgU3RhdHVzX2NsYXNzKQpgYGAKCiMjIyBHcsOhZmljb3MgZXh0cmFzCgpgYGB7ciwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9Nn0KIyByaXNjbyBwb3IgZmFpeGEgZGUgc2NvcmUgKG11bHRpcGxhcyBub3RhcyBkZSBjb3J0ZSldCnBlcmNlbnRpcyA9IDE1CmNyZWRpdF90ZXN0X3ByZWRzICU+JQogIG11dGF0ZSgKICAgIHNjb3JlID0gIGZhY3RvcihudGlsZSgucHJlZF9iYWQsIHBlcmNlbnRpcykpCiAgKSAlPiUKICBjb3VudChzY29yZSwgU3RhdHVzKSAlPiUKICBnZ3Bsb3QoYWVzKHkgPSBzY29yZSwgeCA9IG4sIGZpbGwgPSBTdGF0dXMpKSArCiAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZmlsbCIpICsKICBnZW9tX2xhYmVsKGFlcyhsYWJlbCA9IG4pLCBwb3NpdGlvbiA9ICJmaWxsIikKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9Nn0KIyBncsOhZmljbyBzb2JyZSBvcyBkYSBjbGFzc2UgImJhZCIKY3JlZGl0X3Rlc3RfcHJlZHMgJT4lCiAgbXV0YXRlKAogICAgc2NvcmUgPSBmYWN0b3IobnRpbGUoLnByZWRfYmFkLCBwZXJjZW50aXMpKQogICkgJT4lCiAgZmlsdGVyKFN0YXR1cyA9PSAiYmFkIikgJT4lCiAgZ3JvdXBfYnkoc2NvcmUpICU+JQogIHN1bW1hcmlzZSgKICAgIG4gPSBuKCksCiAgICBtZWRpYSA9IG1lYW4oLnByZWRfYmFkKQogICkgJT4lCiAgbXV0YXRlKHAgPSBuL3N1bShuKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gcCwgeSA9IHNjb3JlKSkgKwogIGdlb21fY29sKCkgKwogIGdlb21fbGFiZWwoYWVzKGxhYmVsID0gc2NhbGVzOjpwZXJjZW50KHApKSwgaGp1c3QgPSAxLjEpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAxL3BlcmNlbnRpcywgY29sb3VyID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIsIHNpemUgPSAxKQpgYGAKCiMjIFBBU1NPIDcpIE1PREVMTyBGSU5BTAoKIyMjIEFqdXN0ZSBmaW5hbAoKYGBge3J9CmNyZWRpdF9maW5hbF9scl9tb2RlbCA8LSBmaXQoY3JlZGl0X3dmLCBjcmVkaXRfZGF0YSkKYGBgCgojIyMgVmFyacOhdmVpcyBkbyBtb2RlbG8gZmluYWwKCmBgYHtyfQojIGV4dHJhaSBvIG1vZGVsbyBkbyBvYmpldG8gZGUgYWp1c3RlCmNyZWRpdF9maW5hbF9scl9maXQgPC0gY3JlZGl0X2ZpbmFsX2xyX21vZGVsJGZpdCRmaXQKCiMgdGFiZWxhIGRlIGJldGFzCnZpKGNyZWRpdF9maW5hbF9scl9maXQpCnZpcChjcmVkaXRfZmluYWxfbHJfZml0KQoKIyBzYWx2YSB0dWRvIG5vIGNvbXB1dGFkb3IKd3JpdGVfcmRzKGNyZWRpdF9maW5hbF9scl9tb2RlbCwgImNyZWRpdF9maW5hbF9scl9tb2RlbC5yZHMiKQp3cml0ZV9yZHMoY3JlZGl0X2xyX2xhc3RfZml0LCAiY3JlZGl0X2xyX2xhc3RfZml0LnJkcyIpCmBgYAoKIyMjIHByZWRpw6fDtWVzCgpgYGB7cn0KIyBwcmVkacOnw7VlcwpkYWRvc19ub3ZvcyA8LSB0ZXN0aW5nKGNyZWRpdF9pbml0aWFsX3NwbGl0KQpwcmVkaWN0KGNyZWRpdF9maW5hbF9scl9tb2RlbCwgbmV3X2RhdGEgPSBkYWRvc19ub3ZvcywgdHlwZSA9ICJwcm9iIikKCiMgY29sb2NhciBubyBCRApkYWRvc19ub3ZvcyA8LSBiaW5kX2NvbHMoCiAgZGFkb3Nfbm92b3MsCiAgcHJlZGljdChjcmVkaXRfZmluYWxfbHJfbW9kZWwsIG5ld19kYXRhID0gZGFkb3Nfbm92b3MsIHR5cGUgPSAicHJvYiIpCikKCiMgc2FsdmFyIG5vIGV4Y2VsCndyaXRleGw6OndyaXRlX3hsc3goZGFkb3Nfbm92b3MsICJkYWRvc19ub3Zvcy54bHN4IikKCiMgc2FsdmFyIG5vIFNRTApjb3B5X3RvKGNvbiwgZGFkb3Nfbm92b3MsIG92ZXJ3cml0ZSA9IFRSVUUsIHRlbXBvcmFyeSA9IEZBTFNFKQpkYkxpc3RUYWJsZXMoY29uKQpgYGAKCiMgW0VYRU1QTE8gREUgREFTSEJPQVJEIChsaW5rKV0oaHR0cHM6Ly9jdXJzby1yLmdpdGh1Yi5pby8yMDIxMDQtaW50cm8tbWwvZXhlbXBsb3MvMTEtcmVwb3J0LWNyZWRpdC1kYXRhLmh0bWwpCg==